package imse.team0.invoice;

import imse.team0.customers.ClientInfoStorage;
import imse.team0.orders.Order;
import imse.team0.transportation.ShipmentPriceReply;
import org.apache.camel.Exchange;
import org.apache.camel.Header;
import org.apache.camel.processor.aggregate.AggregationStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Eugen, 5/5/12 1:27 PM
 */
public class InvoiceBuilder implements AggregationStrategy {

    private static final Logger LOG = LoggerFactory.getLogger(InvoiceBuilder.class);

    private static final String NL = System.getProperty("line.separator");
    private static final String PRICE_DELIM = "#";

    private List<InternetAddress> priceCatalogAddrs;
    private String orderInvoiceFilter;
    private volatile int pcIndex = 0;

    //private int retryCount = 0;

    public void setPriceCatalogAddrs(String addrs) {
        String addr[] = addrs.split(";");
        priceCatalogAddrs = new ArrayList<InternetAddress>();
        for (int i = 0; i < addr.length; i++) {
            LOG.debug("----------------> Price Catalog Address = " + addr[i]);
            String ip_port[] = addr[i].split(":");
            if (ip_port.length != 2) {
                LOG.error("Invalid ip and port pair supplied at configuration startup: " + addr[i]);
                throw new RuntimeException("Invalid ip and port pair supplied at configuration startup: " + addr[i]);
            }
            InternetAddress _addr = new InternetAddress();
            _addr.ip = ip_port[0];
            _addr.port = Integer.parseInt(ip_port[1]);
            priceCatalogAddrs.add(_addr);
        }
        int pcIndex = 0;
    }

    public void setJMSOrderInvoiceFilter(String invoiceFilter) {
        orderInvoiceFilter = invoiceFilter;
        LOG.debug("----------------> Received unique invoice group ID: (" + orderInvoiceFilter + ")");
    }

    public String getInvoiceReceiverAddress(Exchange exchange, @Header("clientID") String clientID) {
        if (exchange.getProperty(Exchange.SLIP_ENDPOINT) != null) {
            String answer = exchange.getIn().getBody(String.class);
            if (answer.equalsIgnoreCase("nack")){
                LOG.warn("Bad xml received by client invoice app. Resending the invoice..."); // infinitely many times .. for the moment
                return Exchange.SLIP_ENDPOINT;
            }
            LOG.debug("----------------> Dynamic Routing of message finished.");
            return null;
        }

        String invoiceReceiverEndpoint = ClientInfoStorage.getClientInvoiceEndpoint(clientID);
        //String invoiceXML = exchange.getIn().getBody(String.class);
        //invoiceXML += "\n";
        //exchange.getIn().setBody(invoiceXML);
        String uri = "netty:tcp://" + invoiceReceiverEndpoint + "?textline=true&keepAlive=false&sync=true";
        LOG.debug("----------------> Routing the invoice to: (" + uri + ")");
        //++retryCount;
        return uri;
    }

    public Exchange aggregate(Exchange currentExchange, Exchange retrievedExchange) {
        Order order = retrievedExchange.getIn().getBody(Order.class);
        ShipmentPriceReply reply = currentExchange.getIn().getBody(ShipmentPriceReply.class);
        LOG.debug("------------->Setting header 'clientID' = " + order.clientID);
        currentExchange.getIn().setHeader("clientID", order.clientID);
        Invoice invoice = new Invoice();
        invoice.setInvoiceGroup(orderInvoiceFilter);
        double totalCost = reply.getPrice();
        invoice.setShipmentCost(totalCost);
        invoice.setOrderId(order.orderid);
        invoice.setCid(currentExchange.getProperty("#MID#").toString());
        List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
        invoice.setInvoiceItems(invoiceItems);
        List<String> orderItemIDs = new ArrayList<String>();
        for (Order.OrderItem item : order.items) {
            orderItemIDs.add(item.itemId);
        }
        List<Double> unitPrices = null;
        try {
            unitPrices = getUnitPrices(orderItemIDs);
        } catch (Exception ex) {
            LOG.error("Could not read prices from Price Catalog !\nCause:" + ex);
            throw new RuntimeException(ex);
        }
        // assuming the order of iteration is consistent
        int i = 0;
        for (Order.OrderItem item : order.items) {
            InvoiceItem invoiceItem = new InvoiceItem();
            invoiceItem.setItemID(item.itemId);
            invoiceItem.setItemQuantity(item.quantity);
            double unitCost = unitPrices.get(i++);
            invoiceItem.setUnitCost(unitCost);
            invoiceItem.setTotalCost(unitCost * item.quantity);
            totalCost += invoiceItem.getTotalCost();
            invoiceItems.add(invoiceItem);
        }
        invoice.setTotalCost(totalCost);
        currentExchange.getIn().setBody(invoice);
        return currentExchange;
    }

    private List<Double> getUnitPrices(List<String> ids) throws Exception {
        int index = roundRobin();
        InternetAddress addr = priceCatalogAddrs.get(index);
        Socket socket = new Socket(addr.ip, addr.port);
        String id_list = "";
        for (String id : ids) {
            id_list += (id + ",");
        }
        id_list = id_list.substring(0, id_list.length() - 1);
        id_list += NL;
        Writer out = new OutputStreamWriter(socket.getOutputStream());
        out.write(id_list);
        out.flush();
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        socket.setSoTimeout(3000);
        String[] s_prices = in.readLine().split(PRICE_DELIM);
        socket.close();
        List<Double> prices = new ArrayList<Double>(s_prices.length);
        for (String s_price : s_prices) {
            prices.add(Double.parseDouble(s_price));
        }
        return prices;
    }

    private int roundRobin() {
        int index = pcIndex;
        pcIndex = (pcIndex+1) % priceCatalogAddrs.size(); // assume it is greater than 0
        return index;
    }
}

class InternetAddress {
    String ip;
    int port;
}
